package common.config.datasource;

import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import common.config.datasource.sharding.config.ShardingSphereAlgorithmConstants;
import common.config.datasource.sharding.table.SysLogShardingConfig;
import common.config.mybatisplus.CustomMetaObjectHandler;
import common.config.mybatisplus.MyBatisPlusConfig;
import common.model.enums.EnvironmentEnum;
import io.seata.rm.datasource.DataSourceProxy;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.shardingsphere.driver.api.ShardingSphereDataSourceFactory;
import org.apache.shardingsphere.infra.config.RuleConfiguration;
import org.apache.shardingsphere.infra.config.mode.ModeConfiguration;
import org.apache.shardingsphere.readwritesplitting.api.ReadwriteSplittingRuleConfiguration;
import org.apache.shardingsphere.readwritesplitting.api.rule.ReadwriteSplittingDataSourceRuleConfiguration;
import org.apache.shardingsphere.sharding.api.config.ShardingRuleConfiguration;
import org.apache.shardingsphere.sharding.api.config.rule.ShardingTableRuleConfiguration;
import org.apache.shardingsphere.sharding.api.config.strategy.sharding.StandardShardingStrategyConfiguration;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.*;

/**
 * 数据源配置<p>
 * 可切换的数据源有：<p>
 * 1. ShardingSphere数据源，包含master、slave01、slave02上的satisfactory数据库，读写分离、分库分表：@DS(DataSourceConstants.SHARDING_DATASOURCE)<p>
 * {@link DataSourceConstants#SHARDING_DATASOURCE}<p>
 * 2. master数据源，仅包含master主机上的satisfactory数据库，无数据分片和读写分离：@DS(DataSourceConstants.MASTER_DATASOURCE)<p>
 * {@link DataSourceConstants#MASTER_DATASOURCE}<p>
 * 3. 慢查询日志数据源，仅包含master主机上的mysql数据库，无数据分片和读写分离：@DS(DataSourceConstants.MYSQL_DATASOURCE)<p>
 * {@link DataSourceConstants#MYSQL_DATASOURCE}<p>
 * 4. information_schema数据源，仅包含master主机上的information_schema数据库，无数据分片和读写分离：@DS(DataSourceConstants.INFORMATION_SCHEMA_DATASOURCE)<p>
 * {@link DataSourceConstants#INFORMATION_SCHEMA_DATASOURCE}<p>
 *
 * @author 米泽鹏
 * @since 2023-7-28 下午 9:26
 */
@Configuration
//@ConditionalOnExpression(value = "'${spring.application.name}'.equals('system')")
@Slf4j
public class DataSourceConfig {

	/**
	 * 动态数据源
	 */
	private static final String DYNAMIC_DATASOURCE = "dynamic";

	/**
	 * 慢查询日志数据源（原始）
	 */
	private static final String ORIGINAL_MYSQL_DATASOURCE = "originalMysql";

	/**
	 * information_schema数据源（原始）
	 */
	private static final String ORIGINAL_INFORMATION_SCHEMA_DATASOURCE = "originalInformationSchema";

	/**
	 * 真实数据节点：master
	 */
	private static final String MASTER_RAW_DATASOURCE = "master";

	/**
	 * 真实数据节点：slave01
	 */
	private static final String SLAVE01_RAW_DATASOURCE = "slave01";

	/**
	 * 真实数据节点：slave02
	 */
	private static final String SLAVE02_RAW_DATASOURCE = "slave02";

	/**
	 * SqlSessionFactory
	 */
	private static final String DYNAMIC_SQL_SESSION_FACTORY = "dynamicSqlSessionFactory";

	@Value("${spring.application.name}")
	private String appName;

	@Resource
	private Environment environment;

	@Resource
	private CustomMetaObjectHandler metaObjectHandler;

	/**
	 * 配置见{@link MyBatisPlusConfig#mybatisPlusInterceptor()}
	 */
	@Resource
	private MybatisPlusInterceptor mybatisPlusInterceptor;

	/**
	 * 非读写分离数据源：mysql
	 */
	@Bean(name = ORIGINAL_MYSQL_DATASOURCE)
	public DataSource mysql() {
		return buildDataSource("3307", "mysql");
	}

	/**
	 * mysql数据源代理
	 */
	@Bean(DataSourceConstants.MYSQL_DATASOURCE)
	public DataSourceProxy dataSourceProxyMysql(@Qualifier(ORIGINAL_MYSQL_DATASOURCE) DataSource druidDataSource) {
		return new DataSourceProxy(druidDataSource);
	}

	/**
	 * 非读写分离数据源：information_schema
	 */
	@Bean(name = ORIGINAL_INFORMATION_SCHEMA_DATASOURCE)
	public DataSource informationSchema() {
		return buildDataSource("3307", "mysql");
	}

	/**
	 * information_schema数据源代理
	 */
	@Bean(DataSourceConstants.INFORMATION_SCHEMA_DATASOURCE)
	public DataSourceProxy dataSourceProxyInformationSchema(@Qualifier(ORIGINAL_INFORMATION_SCHEMA_DATASOURCE) DataSource druidDataSource) {
		return new DataSourceProxy(druidDataSource);
	}

	/**
	 * master数据源代理
	 */
	@Bean(DataSourceConstants.MASTER_DATASOURCE)
	public DataSourceProxy dataSourceProxyMaster(@Qualifier(MASTER_RAW_DATASOURCE) DataSource druidDataSource) {
		return new DataSourceProxy(druidDataSource);
	}

	/**
	 * 真实数据节点：master
	 */
	@Bean(name = MASTER_RAW_DATASOURCE)
	public DataSource master() {
		return buildDataSource("3307", "satisfactory");
	}

	/**
	 * 真实数据节点：slave01
	 */
	@Bean(name = SLAVE01_RAW_DATASOURCE)
	public DataSource slave01() {
		return buildDataSource("3308", "satisfactory");
	}

	/**
	 * 真实数据节点：slave02
	 */
	@Bean(name = SLAVE02_RAW_DATASOURCE)
	public DataSource slave02() {
		return buildDataSource("3309", "satisfactory");
	}

	private DataSource buildDataSource(String port, String databaseName) {
		DruidDataSource druidDataSource = new DruidDataSource();
		druidDataSource.setUrl("jdbc:mysql://localhost:" + port + "/" + databaseName + "?rewriteBatchedStatements=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai");
		// druidDataSource.setUrl("jdbc:mysql://8.130.164.63:" + port + "/" + databaseName + "?rewriteBatchedStatements=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai");
		druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
		druidDataSource.setUsername("root");
		druidDataSource.setPassword("mzp19940219");
		return druidDataSource;
	}

	/**
	 * ShardingSphere数据源代理
	 */
	@Bean(DataSourceConstants.SHARDING_DATASOURCE)
	// 重要：必须在分片配置类加载后才能加载，否则无法获取分片算法
	@DependsOn({"sysLogShardingConfig"})
	public DataSourceProxy dataSource(@Qualifier(MASTER_RAW_DATASOURCE) DataSource master, @Qualifier(SLAVE01_RAW_DATASOURCE) DataSource slave01, @Qualifier(SLAVE02_RAW_DATASOURCE) DataSource slave02) throws SQLException {
		log.info("init sharding datasource");
		// 运行模式类型，可选配置：Memory、Standalone、Cluster，默认为Memory
		ModeConfiguration modeConfiguration = new ModeConfiguration("Memory", null, false);
		// 数据源配置
		Map<String, DataSource> dataSourceMap = new HashMap<>();
		dataSourceMap.put(MASTER_RAW_DATASOURCE, master);
		dataSourceMap.put(SLAVE01_RAW_DATASOURCE, slave01);
		dataSourceMap.put(SLAVE02_RAW_DATASOURCE, slave02);
		// 规则配置，可选配置：数据分片、读写分离、数据加密、影子库、SQL解析、混合规则
		Set<RuleConfiguration> configs = new HashSet<>();
		// 读写分离配置
		configs.add(getReadwriteSplittingRuleConfiguration());
		// 数据分片配置
		ShardingRuleConfiguration shardingRuleConfiguration = new ShardingRuleConfiguration();
		// 初始化所有算法
		shardingRuleConfiguration.getShardingAlgorithms().putAll(ShardingSphereAlgorithmConstants.SHARDING_ALGORITHMS);
		// 需要分片的表（需要分片的表都写在这里，目前只有一张表）
		shardingRuleConfiguration.getTables().add(getSysLogShardingConfiguration());
		configs.add(shardingRuleConfiguration);
		// 其他配置
		Properties properties = new Properties();
		// 在非生产环境打印日志
		if (environment.acceptsProfiles(Profiles.of(EnvironmentEnum.PRODUCT.getName()))) {
			properties.setProperty("sql-show", Boolean.FALSE.toString());
		} else {
			properties.setProperty("sql-show", Boolean.TRUE.toString());
		}
		// 创建数据源代理
		return new DataSourceProxy(ShardingSphereDataSourceFactory.createDataSource(modeConfiguration, dataSourceMap, configs, properties));
	}

	/**
	 * 动态数据源
	 */
	@Bean(DYNAMIC_DATASOURCE)
	public DataSource dynamicDataSource(@Qualifier(DataSourceConstants.SHARDING_DATASOURCE) DataSourceProxy shardingDatasource, @Qualifier(DataSourceConstants.MASTER_DATASOURCE) DataSourceProxy masterDatasource, @Qualifier(DataSourceConstants.MYSQL_DATASOURCE) DataSourceProxy mysqlDatasource, @Qualifier(DataSourceConstants.INFORMATION_SCHEMA_DATASOURCE) DataSourceProxy informationSchemaDatasource) {
		DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
		dynamicRoutingDataSource.addDataSource(DataSourceConstants.SHARDING_DATASOURCE, shardingDatasource);
		dynamicRoutingDataSource.addDataSource(DataSourceConstants.MASTER_DATASOURCE, masterDatasource);
		dynamicRoutingDataSource.addDataSource(DataSourceConstants.MYSQL_DATASOURCE, mysqlDatasource);
		dynamicRoutingDataSource.addDataSource(DataSourceConstants.INFORMATION_SCHEMA_DATASOURCE, informationSchemaDatasource);
		dynamicRoutingDataSource.setPrimary(DataSourceConstants.SHARDING_DATASOURCE);
		dynamicRoutingDataSource.setSeata(true);
		return dynamicRoutingDataSource;
	}

	/**
	 * 创建事务管理器，解决@Transactional注解失效的问题
	 */
	@Bean
	public DataSourceTransactionManager shardingTransactionManger(@Qualifier(DYNAMIC_DATASOURCE) DataSource dataSource) {
		return new DataSourceTransactionManager(dataSource);
	}

	/**
	 * 创建SqlSessionFactory
	 */
	@Bean(DYNAMIC_SQL_SESSION_FACTORY)
	public SqlSessionFactory sqlSessionFactory(@Qualifier(DYNAMIC_DATASOURCE) DataSource dataSource) throws Exception {
		// 这里用 MybatisSqlSessionFactoryBean 代替了 SqlSessionFactoryBean，否则 MyBatisPlus 不会生效
		MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
		mybatisSqlSessionFactoryBean.setDataSource(dataSource);
		mybatisSqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:" + "{id:common|" + appName + "}/mapper/xml/*.xml"));
		mybatisSqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
		// MyBatis-Plus 配置
		MybatisConfiguration mybatisConfiguration = new MybatisConfiguration();
		// MyBatis-Plus的分页、乐观锁等功能是通过MybatisPlusInterceptor插入的
		mybatisConfiguration.addInterceptor(mybatisPlusInterceptor);
		// 在非生产环境打印日志
		if (!environment.acceptsProfiles(Profiles.of(EnvironmentEnum.PRODUCT.getName()))) {
			mybatisConfiguration.setLogImpl(StdOutImpl.class);
		}
		// 返回为null的字段
		mybatisConfiguration.setCallSettersOnNulls(true);
		mybatisSqlSessionFactoryBean.setConfiguration(mybatisConfiguration);
		GlobalConfig globalConfig = new GlobalConfig();
		// 字段自动填充
		globalConfig.setMetaObjectHandler(metaObjectHandler);
		GlobalConfig.DbConfig dbConfig = new GlobalConfig.DbConfig();
		// 主键策略
		dbConfig.setIdType(IdType.AUTO);
		// 查询策略
		dbConfig.setWhereStrategy(FieldStrategy.DEFAULT);
		// 插入策略
		dbConfig.setInsertStrategy(FieldStrategy.DEFAULT);
		// 更新策略（IGNORED表示null值也会更新）
		dbConfig.setUpdateStrategy(FieldStrategy.IGNORED);
		globalConfig.setDbConfig(dbConfig);
		mybatisSqlSessionFactoryBean.setGlobalConfig(globalConfig);
		return mybatisSqlSessionFactoryBean.getObject();
	}

	/**
	 * 创建SqlSessionTemplate
	 */
	@Bean
	public SqlSessionTemplate sqlSessionTemplate(@Qualifier(DYNAMIC_SQL_SESSION_FACTORY) SqlSessionFactory sqlSessionFactory) {
		return new SqlSessionTemplate(sqlSessionFactory);
	}

	/**
	 * 读写分离配置
	 */
	private ReadwriteSplittingRuleConfiguration getReadwriteSplittingRuleConfiguration() {
		// 负载均衡算法配置
		Properties dataSourceProperties = new Properties();
		// 写数据源名称
		dataSourceProperties.setProperty("write-data-source-name", MASTER_RAW_DATASOURCE);
		// 读数据源名称，多个从数据源用逗号分隔
		dataSourceProperties.setProperty("read-data-source-names", SLAVE01_RAW_DATASOURCE + "," + SLAVE02_RAW_DATASOURCE);
		// 自定义的读写分离数据源名称：shardingDatasource
		// 读写分离类型，如: Static，Dynamic
		ReadwriteSplittingDataSourceRuleConfiguration dataSourceRuleConfiguration = new ReadwriteSplittingDataSourceRuleConfiguration(DataSourceConstants.SHARDING_DATASOURCE, "Static", dataSourceProperties, ShardingSphereAlgorithmConstants.READWRITE_ALG_ROUND);
		return new ReadwriteSplittingRuleConfiguration(Collections.singleton(dataSourceRuleConfiguration), ShardingSphereAlgorithmConstants.READWRITE_ALGORITHMS);
	}

	/**
	 * sys_log表分片配置
	 * <p>
	 * 其中主键生成策略使用了MyBatis-Plus提供的主键策略{@link IdType#ASSIGN_ID}
	 */
	private ShardingTableRuleConfiguration getSysLogShardingConfiguration() {
		// 指定逻辑表名和真实数据节点
		ShardingTableRuleConfiguration tableRuleConfiguration = new ShardingTableRuleConfiguration(SysLogShardingConfig.LOGIC_TABLE_NAME, SysLogShardingConfig.ACTUAL_DATA_NODES);
		// 分库策略
//		tableRuleConfiguration.setDatabaseShardingStrategy(new StandardShardingStrategyConfiguration("user_id", ShardingSphereAlgorithmConstants.SHARDING_DATABASE_INLINE));
		// 分表策略
		tableRuleConfiguration.setTableShardingStrategy(new StandardShardingStrategyConfiguration(DataSourceConstants.ID_FIELD_NAME_IN_DATABASE, SysLogShardingConfig.SYS_LOG_TABLE_ALG));
		// 主键生成策略（未使用，而是使用了MyBatis-Plus提供的主键策略IdType.ASSIGN_ID）
//		Properties snowflakeProp = new Properties();
//		snowflakeProp.setProperty("worker-id", "1");
//		shardingRuleConfiguration.getKeyGenerators().put("snowflake", new ShardingSphereAlgorithmConfiguration("SNOWFLAKE", snowflakeProp));
//		tableRuleConfiguration.setKeyGenerateStrategy(new KeyGenerateStrategyConfiguration("id", "snowflake"));
		return tableRuleConfiguration;
	}

}
